#!/usr/bin/env bash

# Disable undeclared variables, exit immediately when exit code != 0
set -eu -o pipefail

############# Const

UPDATE_URL="https://sdk-os-static.mihoyo.com/hk4e_global/mdk/launcher/api/resource?key=gcStgarh&launcher_id=10"
#UPDATE_URL=http://localhost:8000/resource.json
#UPDATE_URL=http://127.0.0.1/resource.json
CONFIG=config.ini
ANCHOR=UnityPlayer.dll
LOG_FILE=_error.txt

# Voice pack information
declare -A LANG_MAP=(
  ["zh-cn"]="Chinese"
  ["en-us"]="English(US)"
  ["ja-jp"]="Japanese"
  ["ko-kr"]="Korean"
)
VOICE_PACK_PATH="GenshinImpact_Data/StreamingAssets/Audio/GeneratedSoundBanks/Windows"

SCRIPT_PATH=`cd "\`dirname \"$0\"\`"; echo "$PWD"`
# Dedicated download directory for aria2c: it stores the index file in the same directory as target file
DOWNLOAD_DIR=$PWD/../_update_gi_download
DOWNLOAD_APPS=("aria2c|aria2c -c" "wget|wget -c" "curl|curl -C")


############# Functions

fatal() {
	echo
	for arg in "$@"; do
	    echo " *  $arg" >&2
	done
	echo

	exit 1
}

download_file() {
	url=$1; md5=$2

	test -n "$url" -a -n "$md5" || fatal "argument missing: download_file <url> <md5>"

	echo "Downloading $url ..."

	archive_file="$DOWNLOAD_DIR/${url##*/}"
	archive_file_completed="${archive_file}.completed"

	if [ -f "$archive_file_completed" ]; then
		# patch was already downloaded but something went wrong when applying it
		echo "-- Found existing archive: ${download_url##*/} is already downloaded"
	else
		mkdir -p "$DOWNLOAD_DIR"
		cd "$DOWNLOAD_DIR"
		$dl_app "$url"
		cd - >/dev/null # Return to previous dir
		touch "$archive_file_completed"
	fi

	echo "-- Verifying MD5 checksum..."
	md5sum -c <<< "$md5 $archive_file"
}

# Perform path sanity checks
# There is a good reason for this check. Do not pollute the game directory.
test \! -e "$SCRIPT_PATH/$ANCHOR" || fatal \
	"Please move this script outside the game directory prior executing." \
	" -> See README.md for proper installation instructions"

# IDEA: Check whether this is a git clone and check for updates


# hidden option to download pre-release version
if [ "x${1:-}" == "x-pre" ]; then
	game_element="pre_download_game"
	end_element="deprecated_packages"
	pre_release=1
else
	game_element="game"
	end_element="plugin"
	pre_release=0
fi

################ Find a supported download executable

dl_app=""
for appname in ${DOWNLOAD_APPS[@]}; do
	dl_app_try=${appname%%|*}
	if command -v $dl_app_try &>/dev/null; then
		dl_app=${appname#*|}
		break
	fi
done

test -n "$dl_app" || fatal \
	"No downloader application found." \
	"Please install 'aria2c' (recommended), 'wget' or 'curl'."


# Check and read out the configuration file
test -e "$CONFIG" || fatal \
	"Game information file $CONFIG not found." \
	"Make sure 'Genshin Impact Game' is the current working directory."

# In case people accidentally want to install the game into the launcher directory
if [ $(ls -b1q *.dll 2>/dev/null | wc -l) -gt 1 ]; then
	fatal "This script is likely run in the wrong directory." \
		"Found more than one DLL file. (expected: 1 or 0)" \
		"Please run this script in a proper/clean game directory."
fi

installed_version=`sed -n 's/^game_version=\(.*\)/\1/p' "$CONFIG" | tr -d "\r\n"`

test -n "$installed_version" || fatal "Cannot read game_version from $CONFIG"
echo "Installed version:  $installed_version"


################ Download the version information JSON file

# wget and curl can download to stdout, but aria2c can't.
# so we just create temp dir for it and read any downloaded file whose name can differ too
tmp_path=$(mktemp -d)
trap "rm -rf '$tmp_path'" EXIT

update_content_src=$(cd "$tmp_path" && "$dl_app" "$UPDATE_URL" >"$LOG_FILE" 2>&1 && cat resource*) || fatal \
	"Failed to download version info. Check your internet connection." \
	"$LOG_FILE may contain some useful information."

# trying to cut out the game info data. Tricky and not 100% realiable
update_content=$(sed "s/^.*\"$game_element\":{//;s/,\"$end_element\":.*$//;s/{/&\n/g;s/}/\n&/g" <<< "$update_content_src")

# find latest version: extract from JSON format
latest_version_content=$(sed -n '/"latest":/,/^}/{/"version":/!d;s/,/\n/g;s/"//g;p}' <<< "$update_content")

declare -A version_info
while read -r keyvalue; do
	version_info[${keyvalue%%:*}]=${keyvalue#*:}
done <<< "$latest_version_content"

echo "Latest version:     ${version_info[version]}"

if [ "${version_info[version]}" = "$installed_version" ]; then
	echo
	echo "==> Client is up to date."

	if [[ "$update_content_src" == *'"pre_download_game":{'* ]]; then
		echo "NOTE: Pre-release client is available."
	fi

	exit 0
fi

# Patch script directory
patcher_dir="${SCRIPT_PATH}/../${version_info[version]//./}"

test -f "$patcher_dir/patch.sh" || fatal \
	"No suitable patch script found. Please check the bug tracker for details about the progress."

# find suitable patch: extract version information
diffs_content=$(sed -n '/"diffs":/,/^}]}]/{/"name":/!d;s/"//g;p}' <<< "$update_content")

patch_strategy="full" # download full client
patch_voicepacks=()
while read -r diff; do
	# we're found the correct version, now reading voicepacks
	if [ "$patch_strategy" == "diff" ]; then
		# if line doesn't contain "language" it isn't the voicepack
		if [[ ! "$diff" =~ "language:" ]]; then
			break
		fi
		patch_voicepacks+=("$diff")
		continue
	fi

	diff_content=$(sed 's/,/\n/g' <<< "$diff")

	unset diff_info
	declare -A diff_info
	while read -r keyvalue; do
		diff_info[${keyvalue%%:*}]=${keyvalue#*:}
	done <<< "$diff_content"

	if [ "${diff_info[version]:-}" == "$installed_version" ]; then
		patch_strategy="diff"
		# continue reading voicepacks
	fi
done <<< "$diffs_content"

if [ "$patch_strategy" == "diff" ]; then
	download_url=${diff_info[path]}
	patch_size=${diff_info[size]}
	patch_md5=${diff_info[md5]}
	patch_type="incremental update"
else
	download_url=${version_info[path]}
	patch_size=${version_info[size]}
	patch_md5=${version_info[md5]}
	patch_type="full client"
	patch_voicepacks=($(sed -n '/"latest":/,/"diffs":/{!d;/"language":/!d;s/"//g;p}' <<< "$update_content"))
	echo "[WARNING] Cannot find any suitable upgrade to ${version_info[version]}. Will download full client!"
fi


################ Add links to the download queue "download_files"

echo

patch_size_mib=$(($patch_size / (1024 * 1024 * 2)))
echo "Main URL: $download_url"
echo "Installed size:     ${patch_size_mib} MiB (${patch_type})"

# array of files to download "[url]=md5"
unset download_files
declare -A download_files

# add main game file/patch
download_files["$download_url"]="$patch_md5"

# extract voicepacks versions
for voicepack in "${patch_voicepacks[@]}"; do
	voicepack_content=$(sed 's/,/\n/g' <<< "$voicepack")

	unset voicepack_info
	declare -A voicepack_info
	while read -r keyvalue; do
		voicepack_info[${keyvalue%%:*}]=${keyvalue#*:}
	done <<< "$voicepack_content"

	# filter the installed languages ${voicepack_info[language]}
	# language:LANGUAGE_NAME,name:ARCHIVE_NAME,path:LINK_TO_ZIP,size:NUMBER,md5:MD5SUM_CAPS
	language=${voicepack_info[language]}
	voice_path=$VOICE_PACK_PATH/${LANG_MAP[$language]}
	if [ -d "$voice_path" ]; then
		download_files[${voicepack_info[path]}]=${voicepack_info[md5]}
		patch_size_mib=$((${voicepack_info[size]} / (1024 * 1024 * 2)))
		echo "$language voice pack URL: ${voicepack_info[path]}"
		echo "Installed size:     ${patch_size_mib} MiB (${patch_type})"
	fi
done

echo

if [ $pre_release -eq 1 ]; then
	echo "ATTENTION: You're going to install pre-release version."
	echo "The wine patch may not be yet available for this version."
	echo "Use on your own risk."
	echo
fi

# confirm update
while :; do
	read -r -p "Start/continue update? [Y/n]: " input
	case "$input" in
		Y|y|'')
			break
			;;

		n|N)
			exit 0
			;;
	esac
done


echo "Download queue:"
for url in "${!download_files[@]}"; do
    echo ">> $url"
done
echo

######## Start/continue downloading patch or full client and voice packs
downloaded_files=()
for url in "${!download_files[@]}"; do
	download_file "$url" "${download_files[$url]}"
	downloaded_files+=("$archive_file")
done


######## Apply clean client update

# Run 'patch_revert.sh' of the newest directory
if [ -e launcher.bat ]; then
	echo
	echo "============== Reverting previous Wine patch ==============="
	bash "$patcher_dir/patch_revert.sh"
	echo "============================================================"
	echo
fi


# Unpack the game files and remove old ones according to deletefiles.txt

echo "-- Updating game files...."

for archive_file in "${downloaded_files[@]}"; do
	# Prevent deleting of the possibly necessary files in case of full update
	rm -f deletefiles.txt

	echo "Unpacking $archive_file..."
	unzip -o "$archive_file" >>"$LOG_FILE"

	if [ -f deletefiles.txt ]; then
		while read -r delme; do
			rm -f "$delme"
		done < deletefiles.txt
		rm deletefiles.txt
	fi
done

sed -i "s/game_version=$installed_version/game_version=${version_info[version]}/" "$CONFIG"

# If we got this far, this was a successful update. Clean up old files.
rm -r "$DOWNLOAD_DIR"
rm -f "$LOG_FILE"

echo "==> Update to version ${version_info[version]} completed"



######## Run wine compatibility patch script

echo
echo "================= Applying new Wine patch =================="
bash "$patcher_dir/patch.sh"
if [ -f "$patcher_dir/patch_anti_logincrash.sh" ]; then
	bash "$patcher_dir/patch_anti_logincrash.sh"
fi
echo "============================================================"
echo "==> Update script completed successfully"
exit 0
